﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Net;
using System.IO;
using Procure.Payment;

namespace Procure.Payment.Authorize
{
    public class AuthorizeGateway : BaseGateway<AuthorizeGatewaySettings>
    {
        private const int ResponseCode = 0;
        private const int ResponseSubCode = 1;
        private const int ResponseReasonCode = 2;
        private const int ResponseReasonText = 3;
        private const int ResponseAuthorizationCode = 4;
        private const int ResponseAVSCode = 5;
        private const int ResponseTransactionId = 6;
        private const int ResponseDescription = 7;
        private const int ResponseCardCode = 38;

        #region Temp
        public override Guid Id { get { return new Guid("FBFA34B2-06F1-4155-9719-F9C193264CB7"); } }

        public override string Name { get { return "Authorize.Net"; } }

        public override bool CanAuthorize { get { return true; } }
        public override bool CanSettle { get { return true; } }
        public override bool CanCharge { get { return true; } }
        public override bool CanRefund { get { return true; } }
        public override bool CanVoid { get { return true; } }

        public override TransactionResult Authorize(TransactionData data)
        {
            return RunTransaction(TransactionType.Authorize, data);
        }

        public override TransactionResult Settle(TransactionData data)
        {
            return RunTransaction(TransactionType.Settle, data);
        }

        public override TransactionResult Charge(TransactionData data)
        {
            return RunTransaction(TransactionType.Charge, data);
        }

        public override TransactionResult Refund(TransactionData data)
        {
            return RunTransaction(TransactionType.Refund, data);
        }

        public override TransactionResult Void(TransactionData data)
        {
            return RunTransaction(TransactionType.Void, data);
        }
        #endregion

        #region RunTransaction
        private TransactionResult RunTransaction(TransactionType transType, TransactionData data)
        {
            TransactionResult result = new TransactionResult();

            String post_string = GetPostString(transType, data);

            Uri post_url = Settings.IsProduction ? Settings.Production : Settings.Test;

            string respString = HttpRequest(post_url, post_string);
            
            string[] response = respString.Split(',');

            StringBuilder Error = new StringBuilder();
            char[] pipe = new char[] { '|' };

            string responseCode = response[ResponseCode].Trim(pipe);
            string responseDescription = response[ResponseDescription].Trim(pipe);
            string responseAVSCode = response[ResponseAVSCode].Trim(pipe);

            result.Successful = (responseCode == "1");

            if (result.Successful)
            {
                // Returned Response Code
                result.Code = responseCode;

                // Response Description
                result.Description = responseDescription;
                // Returned Authorisation Code
                result.AuthCode = response[ResponseAuthorizationCode].Trim(pipe);
                // Response AVS Code
                result.AddressVerificationCode = responseAVSCode;
                // Response AVS Code
                result.TransactionId = response[ResponseTransactionId].Trim(pipe);

            }
            else
            {

                // Error!
                string responseReasonCode = response[ResponseReasonCode].Trim(pipe);
                Error.AppendFormat("{0} ({1})", responseDescription, responseReasonCode);

                if (response[ResponseReasonCode].Trim(pipe) == "44")
                {
                    // CCV transaction decline
                    Error.Append("Our Card Code Verification (CCV) returned the following error: ");
                    string responseCardCode = response[ResponseCardCode].Trim(pipe);
                    Error.Append(GetCardCodeResponseMessage(responseCardCode));
                }

                else if (responseReasonCode == "45")
                {
                    if (Error.Length > 1)
                        Error.Append("<br />\n");

                    // AVS transaction decline
                    Error.Append("Our Address Verification System (AVS) returned the following error: ");

                    Error.Append(GetResponseAVSCodeMessage(responseAVSCode));
                }
            }

            result.Message = Error.ToString();

            return result;
        }
        #endregion

        #region GetCardCodeResponseMessage
        private static string GetCardCodeResponseMessage(string creditCardResponse)
        {
            string error = string.Empty;
            switch (creditCardResponse)
            {
                case "N":
                    error = "Card Code does not match.";
                    break;
                case "P":
                    error = "Card Code was not processed.";
                    break;
                case "S":
                    error = "Card Code should be on card but was not indicated.";
                    break;
                case "U":
                    error = "Issuer was not certified for Card Code.";
                    break;
            }
            return error;
        }
        #endregion

        #region GetResponseAVSCodeMessage
        private static string GetResponseAVSCodeMessage(string responseAVSCode)
        {
            string error = string.Empty;
            switch (responseAVSCode)
            {
                case "A":
                    error = " the zip code entered does not match the billing address.";
                    break;
                case "B":
                    error = " no information was provided for the AVS check.";
                    break;
                case "E":
                    error = " a general error occurred in the AVS system.";
                    break;
                case "G":
                    error = " the credit card was issued by a non-US bank.";
                    break;
                case "N":
                    error = " neither the entered street address nor zip code matches the billing address.";
                    break;
                case "P":
                    error = " AVS is not applicable for this transaction.";
                    break;
                case "R":
                    error = " please retry the transaction; the AVS system was unavailable or timed out.";
                    break;
                case "S":
                    error = " the AVS service is not supported by your credit card issuer.";
                    break;
                case "U":
                    error = " address information is unavailable for the credit card.";
                    break;
                case "W":
                    error = " the 9 digit zip code matches, but the street address does not.";
                    break;
                case "Z":
                    error = " the zip code matches, but the address does not.";
                    break;
            }
            return error;
        }
        #endregion

        #region GetPostString
        private String GetPostString(TransactionType transType, TransactionData data)
        {
            Hashtable post_values = new Hashtable();

            post_values.Add("x_version", Settings.Version);

            post_values.Add("x_login", Settings.LoginId);
            post_values.Add("x_tran_key", Settings.TransKey);

            post_values.Add("x_delim_data", "TRUE");
            post_values.Add("x_delim_char", ',');
            post_values.Add("x_encap_char", '|');
            post_values.Add("x_relay_response", "FALSE");

            switch (transType)
            {
                case TransactionType.Authorize:
                    post_values.Add("x_type", "AUTH_ONLY");
                    break;
                case TransactionType.Settle:
                    post_values.Add("x_type", "PRIOR_AUTH_CAPTURE");
                    break;
                case TransactionType.Charge:
                    post_values.Add("x_type", "AUTH_CAPTURE");
                    break;
                case TransactionType.Refund:
                    post_values.Add("x_type", "CREDIT");
                    break;
                case TransactionType.Void:
                    post_values.Add("x_type", "VOID");
                    break;
            }

            post_values.Add("x_method", "CC");
            post_values.Add("x_card_num", "4111111111111111");
            post_values.Add("x_exp_date", "0115");

            post_values.Add("x_amount", data.Total);
            post_values.Add("x_description", "Sample Transaction");

            post_values.Add("x_first_name", "John");
            post_values.Add("x_last_name", "Doe");
            post_values.Add("x_address", "1234 Street");
            post_values.Add("x_state", "WA");
            post_values.Add("x_zip", "98004");
            if (!Settings.IsProduction)
                post_values.Add("x_test_request", "True");

            String post_string = "";
            foreach (DictionaryEntry field in post_values)
            {
                post_string += field.Key + "=" + field.Value + "&";
            }
            post_string = post_string.TrimEnd('&');
            return post_string;
        }
        #endregion

        #region HttpRequest
        private static string HttpRequest(Uri post_url, String post_string)
        {
            // create an HttpWebRequest object to communicate with Authorize.net
            HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(post_url);
            objRequest.Method = "POST";
            objRequest.ContentLength = post_string.Length;
            objRequest.ContentType = "application/x-www-form-urlencoded";

            // post data is sent as a stream
            StreamWriter myWriter = null;
            myWriter = new StreamWriter(objRequest.GetRequestStream());
            myWriter.Write(post_string);
            myWriter.Close();

            // returned values are returned as a stream, then read into a string
            String post_response;
            HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
            using (StreamReader responseStream = new StreamReader(objResponse.GetResponseStream()))
            {
                post_response = responseStream.ReadToEnd();
                responseStream.Close();
            }

            // the response string is broken into an array
            // The split character specified here must match the delimiting character specified above
            return post_response;
        }
        #endregion

        public override AuthorizeGatewaySettings Settings { get; set; }

        public AuthorizeGateway(Dictionary<string, string> GatewaySettings) : base(GatewaySettings)
        {
            Settings = new AuthorizeGatewaySettings(GatewaySettings);
        }
    }
}
